package TangramCore;

import TangramCore.Database.ObrazecData.HexadecInputStream;
import TangramCore.Database.ObrazecData.HexadecOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Vector;
import java.util.List;
import java.text.Collator;
import java.util.Arrays;
import java.util.Set;

public class Database {
    private String sklNazvy[];
    private HashMap<String,Skladacka> skladacky;
    private HashMap<String,ObrazecData> defObrazce;
    private Skladacka actSet, clipboardSet;
    private String actDatabase = null;
    private Vector<ObrazecData> data = null, dataSel = null, dataFiltered = null, clipboard = null;
    private static final Collator collator = Collator.getInstance();
    public Comparator<ObrazecData> cmpName = new ObrazecData.NameComparator(collator);
    public Comparator<ObrazecData> cmpAuthor = new ObrazecData.AuthorComparator(collator);
    public Comparator<ObrazecData> cmpDate = new ObrazecData.DateComparator(collator);
    
    private boolean changed, events = true;
    public static final int CHANGED_NOTHING = 0, CHANGED_SELECTION = 1, CHANGED_OBRAZEC = 2, CHANGED_DESCRIPTION = 4, CHANGED_ALL = 7;
    public Class<? extends Object> parent;
    
    public Database(Class<? extends Object> cl) {
        collator.setStrength(Collator.TERTIARY);        
        skladacky = new HashMap<String,Skladacka>();
        parent = cl;
        //readOnly = parent != null;
    }
    public Database(String dirSkl, Class<? extends Object> cl) {
        this(cl);
        addSetsDir(dirSkl);//addSetsList, addSet
        initSets(cl);
    }

    List<DatabaseChangeListener> listeners = new LinkedList<DatabaseChangeListener>();
    public void addDatabaseChangeListener(DatabaseChangeListener listener){
        listeners.add(listener);
    }
    public void removeDatabaseChangeListener(DatabaseChangeListener listener){
        if(listeners.contains(listener))
            listeners.remove(listener);
    }
    
    public interface DatabaseChangeListener {
        public boolean databaseChanged(int what, int i);
        public boolean showDocument(boolean jak);    
    }
    
    public static class ObrazecFilter {
        public boolean filterContaining, filterDated, filterKeywords;
        public int filterSimilar; //0 == false, 1 == shape, 2 == solution
        public boolean searchNazev, searchPopis, searchAutor, datedFrom, datedTo;
        public String sContaining[];
        public Vector<String> sKeywords;
        public Date dateFrom, dateTo;
        public Obrys oSimilar;
        public Dilek[] dSimilar;
        public Skladacka skladacka;
        public double tol, tola;
        public static int FILTER_NEW = 0, FILTER_INTERSECT = 1, FILTER_SUBTRACT = 2, FILTER_ADD = 3;
        public int filterType;
        
        public boolean accept(ObrazecData od){
            if(filterKeywords){
                if(!od.keywords.containsAll(sKeywords))
                    return false;
                //for(int i=0; i<sKeywords.length; i++)
                //  if(Collections.binarySearch(od.keywords,sKeywords[i])<0)
                //    return false;
            }
            if(filterContaining && (searchNazev || searchPopis || searchAutor)){
                for(int i=0; i<sContaining.length; i++){
                    if (searchNazev && od.nazev.toLowerCase().indexOf(sContaining[i])>-1)
                        continue;
                    if (searchPopis && od.popis.toLowerCase().indexOf(sContaining[i])>-1)
                        continue;
                    if (searchAutor && od.autor.toLowerCase().indexOf(sContaining[i])>-1)
                        continue;
                    return false;
                }
            }
            if(filterDated && (datedFrom || datedTo)){
                if(datedFrom && od.datum.before(dateFrom))
                    return false;
                if(datedTo && od.datum.after(dateTo))
                    return false;
            }
            if(filterSimilar == 1){
                if(!oSimilar.compare(od.o,tol,false))
                    return false;
            } else if(filterSimilar == 2){
                if(!skladacka.isSameSolution(od.d, dSimilar, tol, tola))
                    return false;
            }
            return true;
        }
    }

    public static class ObrazecData {
        public static final String defNazev = "New_figure", defAutor = "traditional", defPopis = "no_description";
        public Skladacka skl;
        public String nazev, popis, autor;//, keywords[];
        public Vector<String> keywords;
        public Date datum;
        //public Poloha p[];
        public Dilek d[];
        public Obrys o;
        boolean selected = false;
    
        public static class NameComparator implements Comparator<ObrazecData>{
            Collator c;
            public NameComparator(Collator c){
                this.c = c;
            }
            public int compare(ObrazecData o1, ObrazecData o2) {
                int ret = c.compare(o1.nazev, o2.nazev);
                if(ret == 0)
                    ret = o1.datum.compareTo(o2.datum);
                if(ret == 0)
                    ret = c.compare(o1.autor, o2.autor);
                return ret;
            }
        }

        public static class DateComparator implements Comparator<ObrazecData>{
            Collator c;
            public DateComparator(Collator c){
                this.c = c;
            }
            public int compare(ObrazecData o1, ObrazecData o2) {
                int ret = o1.datum.compareTo(o2.datum);
                if(ret == 0)
                    ret = c.compare(o1.nazev, o2.nazev);
                if(ret == 0)
                    ret = c.compare(o1.autor, o2.autor);
                return ret;
            }
        }

        public static class AuthorComparator implements Comparator<ObrazecData>{
            Collator c;
            public AuthorComparator(Collator c){
                this.c = c;
            }
            public int compare(ObrazecData o1, ObrazecData o2) {
                int ret = c.compare(o1.autor, o2.autor);
                if(ret == 0)
                   ret = c.compare(o1.nazev, o2.nazev);
                if(ret == 0)
                   ret = o1.datum.compareTo(o2.datum);                    
                return ret;
            }
        }
       
        private void initDescription() {
            nazev = defNazev;
            autor = defAutor;
            datum = new Date();
            keywords = new Vector<String>();
            popis = defPopis;
        }
        
        public ObrazecData(ObrazecData od, Dilek d[]){
            this(od, d, new Obrys(d, od.o.od));
        }
        public ObrazecData(ObrazecData od, Dilek dOrig[], Obrys oOrig){//deep copy of d and o
            skl = od.skl;
            nazev = new String(od.nazev);
            popis = new String(od.popis);
            autor = new String(od.autor);
            keywords = new Vector<String>(od.keywords); //? nebo vkladat kopie klicovych slov?
            datum = new Date(od.datum.getTime());
            d = skl.getDilky(dOrig);
            this.o = (oOrig == null) ? new Obrys(d) : new Obrys(d, oOrig.od);
        }
        
        public ObrazecData(ObrazecData od){//deep copy
            this(od, od.d, od.o);
        }
        
        public ObrazecData(Skladacka s) {
            skl = s;
            initDescription();
        }
        
        public ObrazecData(Skladacka s, Poloha pol[], Obrys.ObrysData od) {
            skl = s;
            d = skl.getDilky(pol);
            o = new Obrys(d, od);
            initDescription();
        }
        
        public ObrazecData(Skladacka s, Dilek dil[]){
            this(s, dil, null);
        }
        
        public ObrazecData(Skladacka s, Dilek dil[], Obrys.ObrysData od) {
            skl = s;
            d = skl.getDilky(dil);
            o = od == null ? new Obrys(d) : new Obrys(d, od);
            initDescription();
        }
        
        public void shallowCopy(ObrazecData od){
            skl = od.skl;
            nazev = od.nazev;
            popis = od.popis;
            autor = od.autor;
            keywords = od.keywords; //? nebo vkladat kopie klicovych slov?
            datum = od.datum;
            o = od.o;
            d = od.d;
        }

        public String uloz(){
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            HexadecOutputStream hex = new HexadecOutputStream(out); 
            try{
                uloz(new DataOutputStream(hex), false);
            }catch(IOException ex){return "";}
            return out.toString();
        }

        public boolean nacti(String data){
            ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes());
            HexadecInputStream hex = new HexadecInputStream(in); 
            try{
                nacti(new DataInputStream(hex));
            }catch(IOException ex){return false;}
            return true;
        }

        public void uloz(DataOutputStream out, boolean sObrysem) throws IOException {
            skl.ulozObrazec(d, sObrysem ? o.od : null, out);
            out.writeUTF(nazev);
            out.writeUTF(popis);
            out.writeUTF(autor);
            out.writeLong(datum.getTime());
            out.writeInt(keywords.size());
            for (int i = 0; i < keywords.size(); i++)
                out.writeUTF(keywords.get(i));
        }
        
        public static class HexadecInputStream extends FilterInputStream {
            public HexadecInputStream(InputStream in){
                super(in);
            }
            @Override
            public int available() throws IOException {
                return super.available()/2;
            }
            @Override
            public int read() throws IOException {
                byte b[] = new byte[2];
                int err = super.read();
                if(err == -1)return err;
                b[0] = (byte)err;
                err = super.read();
                if(err == -1)return err;
                b[1] = (byte)err;
                String s = new String(b);
                return Integer.parseInt(s, 16);
            }
            @Override
            public int read(byte[] b) throws IOException {
                for(int i = 0; i<b.length; i++)
                   b[i] = (byte)read();
                return b.length;
            }
            
            @Override
            public int read(byte[] b, int off, int len) throws IOException {
                byte[] bb = new byte[2*len];
                int count = super.read(bb, 0, 2*len);
                if(count % 2 == 1){
                    b[2*len - 1] = (byte)super.read();
                    count++;
                }
                count = count / 2;
                for(int i = 0; i<count; i++){
                    String s = new String(bb, 2*i, 2);
                    b[off + i] = (byte)Integer.parseInt(s, 16);
                }
                return count;
            }
            @Override
            public long skip(long n) throws IOException {
                return super.skip(2*n)/2;
            }
        }
        public static class HexadecOutputStream extends FilterOutputStream {
            private static byte[] digits = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46};
            public HexadecOutputStream(OutputStream out){
                super(out);
            }
            @Override
            public void write(int b) throws IOException {
                if(b < 0 || b > 0xFF)
                    throw new IOException();
                int val = (int)b & 0xFF;
                super.write(digits[val / 0x10]);
                super.write(digits[val % 0x10]);
            }
            @Override
            public void write(byte[] b) throws IOException {
                for(int i = 0; i<b.length; i++){
                    int val = (int)b[i] & 0xFF;
                    super.write(digits[val / 0x10]);
                    super.write(digits[val % 0x10]);
                }
            }
            @Override
            public void write(byte[] b, int off, int len) throws IOException {
                for(int i = off, to = off + len; i < to; i++){
                    int val = (int)b[i] & 0xFF;
                    super.write(digits[val / 0x10]);
                    super.write(digits[val % 0x10]);
                }
            }
        }          

        
        public void nacti(DataInputStream in) throws IOException {
            //Poloha p[] = skl.nactiObrazec(in, od);
            //d = skl.getDilky(p);
            d = skl.nactiObrazec(in); //misto od null pokud se ma Obrys vypocitat z pozice dilku
            //Dilek.normalizeAll(d);
            Obrys.ObrysData od = new Obrys.ObrysData();
            od.readFromFile(in);
            o = od.isEmpty() ? new Obrys(d) : new Obrys(d, od);
            nazev = in.readUTF();
            popis = in.readUTF();
            autor = in.readUTF();
            datum = new Date(in.readLong());
            int count = in.readInt();
            keywords = new Vector<String>(count+10);
            for (int i = 0; i < count; i++)
                keywords.add(in.readUTF());
        }
    }
    
/*    public static boolean getResourceAsStream(String res){
        try{
        Enumeration<URL> en = Database.class.getClassLoader().getResources("*");
        while(en.hasMoreElements())
        System.out.println(en.nextElement().toString());
        
        return true;
        }catch(IOException ex){
            ex.printStackTrace();
        }
        return true;
        //String s = parent.getResource(res).toString();
        //return parent.getResourceAsStream(res);
    }*/
            
    public int addSetFile(String setFile, Class<? extends Object> cl){
        if(setFile.endsWith(Skladacka.defExt)){
            Skladacka s = Skladacka.fromFile(setFile, cl);    
            skladacky.put(s.nazev, s);
            return 1;
        }
        return 0;
    }

    public int addSetsDir(String dir) {
        int count = 0;
        try{
            if(dir != null){
                File ff[] = (new File(dir)).listFiles();
                for (File f: ff)
                    count += addSetFile(dir + f.getName(), null);
            }
        }
        catch(Exception ex){}   
        return count;
    }
    public void initSets(Class<? extends Object> cl){//cl 
        Set<String> keys = skladacky.keySet();
        String sNazev[] = new String[keys.size()];
        keys.toArray(sNazev);
        sklNazvy = new String[sNazev.length];
        for (int i = 0; i < sNazev.length; i++)
            sklNazvy[i] = sNazev[i];
        Arrays.sort(sklNazvy);
        loadDefaultObrazec(cl);
    }
    
    public int addSetsList(String lstFile) {
        int count = 0;
        try {
            InputStream is = parent.getResourceAsStream(lstFile);
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader in = new BufferedReader(isr);
            int i = lstFile.lastIndexOf('/');
            String line, sklDir = i > 0 ? lstFile.substring(0, i + 1) : "";
            while((line = in.readLine())!= null)
                count += addSetFile(sklDir + line, parent);
            in.close();
            isr.close();
            is.close();
        }
        catch(Exception ex){}
        return count;
    }
    public String[] getSetsSorted() {
        return sklNazvy;
    }
    
    public Skladacka getSet(String nazev) {
        return (Skladacka) skladacky.get(nazev);
    }
    
    public void create(String sklName) {
        actSet = getSet(sklName);
        actDatabase = null;
        if (data != null){
            data.clear();
            dataSel.clear();
        } else{
            data = new Vector<ObrazecData>(100);
            dataSel = new Vector<ObrazecData>(100);
        }
        dataFiltered = null;
        changed(CHANGED_ALL, -1, true);
    }
    
    int skippedWhat = CHANGED_NOTHING;
    int skippedi = -2;
    public void changed(int what, int i, boolean realChange) {
        changed |= realChange;
        skippedWhat |= what;
        if(i > -2){
            if(skippedi < -1)
                skippedi = i;
            else if(skippedi > -1)
                skippedi = -1;
        }
        if(events && !skipEvent){
            List<DatabaseChangeListener> lstRemove = new LinkedList<DatabaseChangeListener>();
            for(DatabaseChangeListener listener: listeners)
                if(listener.databaseChanged(skippedWhat, skippedi)==false)
                    lstRemove.add(listener);
            for(DatabaseChangeListener listener: lstRemove)
                listener.showDocument(false);
            skippedWhat = CHANGED_NOTHING;
            skippedi = -2;
        }
        skipEvent = false;
    }
    
    private boolean open(InputStream is, ProgressListener pl) {
        boolean ret = true;
        try {
            DataInputStream in = new DataInputStream(is);
            Skladacka newSet = getSet(in.readUTF());
            if(newSet != null){
                actSet = newSet;
                //actDatabase = sf;
                //TODO name = sf.FileName
                int count = in.readInt();
                if(pl != null)
                  pl.progressStart(1, count);
                data = new Vector<ObrazecData>(count + 100);
                dataSel = new Vector<ObrazecData>(count + 100);
                for (int i = 0; i < count; i++) {
                    if(pl != null)
                        pl.progressChanged(i+1);
                    ObrazecData obd = new ObrazecData(actSet);
                    obd.nacti(in);
                    data.add(obd);
                    if(obd.selected)
                        dataSel.add(obd);
                }
                dataFiltered = null;
                changed = false;
                changed(CHANGED_ALL, -1, false);
            }
            else{
              System.out.println("Puzzle name found in the database does not belong to any known puzzle definition!");
              ret = false;
            }
            in.close();
            is.close();
        } catch (IOException ex) {
            skipEvent = false;
            ret = false;
        }
        if(pl != null)
            pl.progressEnd(ret);
        return true;
    }

    public boolean openFile(String sf, ProgressListener pl) {
        try {
            InputStream is = parent == null ? new FileInputStream(sf) : parent.getResourceAsStream(sf);
            if(open(is, pl)){
                actDatabase = sf;
                return true;
            }
            //file corrupted
        }catch(FileNotFoundException ex){}
        return false;//file not found
    }
    
    public boolean save(DataOutputStream out, boolean sObrysy, ProgressListener pl){
        int count = data.size();
        boolean ret = false;
        if(pl != null)
            pl.progressStart(1, count);
        try {
            out.writeUTF(actSet.nazev);
            out.writeInt(count);
            for (int i = 0; i < count; i++) {
                if(pl != null)
                    pl.progressChanged(i+1);
                ObrazecData od = data.get(i);
                od.uloz(out, sObrysy);
            //actSet.ulozObrazec(od.d, od.o.od, out);
            }
            out.close();    
            ret = true;
        } catch (IOException ex) {
            ret = false; //System.out.println("Cannot write file " + f + ".");
        }
        if(pl != null)
            pl.progressEnd(ret);
        return true;
    }
    
    public boolean saveAs(String f, ProgressListener pl) {
        boolean ret = false;
        if(pl != null)
            pl.progressStart(1, data.size());
        try {
            DataOutputStream out = new DataOutputStream(new FileOutputStream(f));
            ret = save(out, true, pl);
            actDatabase = f;
        } catch (FileNotFoundException ex) {
            skipEvent = false;
            System.out.println("Cannot create file " + f + ".");
        }
        //TODO name = f.FileName
        changed = false;
        changed(CHANGED_SELECTION, -1, false);
        return ret;
    }
    
    public String exportWeb(){
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        HexadecOutputStream hex = new HexadecOutputStream(out); 
        save(new DataOutputStream(hex), false, null);
        return out.toString();
    }
    
    public boolean openWeb(String data){
        ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes());
        HexadecInputStream hex = new HexadecInputStream(in); 
        open(new DataInputStream(hex), null);
        return true;
    }
    
    public boolean save(ProgressListener pl) {
        if (actDatabase == null){
            if(pl != null){
                skipEvent = false;
                pl.progressEnd(false);
            }
            return false;
        }
        saveAs(actDatabase, pl);
        return true;
    }
    
    public int getSizeAll() {
        return data == null ? 0 : data.size();
    }
    public int getSize() {
        Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
        return d == null ? 0 : d.size();
    }
    
    public ObrazecData getObrazecData(int i) {
        Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
        return i < d.size() ? d.get(i) : null;
    }
    
    public boolean isSelected(int i) {
        Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
        if (d == null)
            return false;
        return i < d.size() ? d.get(i).selected : false;
    }
    
    public void setSelection(int i, boolean jak) {
        Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
        ObrazecData obd = d.get(i);
        if(obd.selected != jak){
            if(obd.selected = jak)
                dataSel.add(obd);
            else
                dataSel.remove(obd);
        }
        changed(CHANGED_SELECTION, i, false);
    }
    
    public void toggleSelection(int i) {
        Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
        ObrazecData obd = d.get(i);
        if(obd.selected = !obd.selected)
            dataSel.add(obd);
        else
            dataSel.remove(obd);
        changed(CHANGED_SELECTION, i, false);
    }
    
    public void selectAll(boolean jak) {
        if(jak == false){
            for (int i = 0; i < dataSel.size(); i++)
                dataSel.get(i).selected = false;
            dataSel.clear();
        } else{
            Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
            for (int i = 0; i < d.size(); i++)
                d.get(i).selected = true;
            dataSel.clear();
            dataSel.addAll(d);
        }
        changed(CHANGED_SELECTION, -1, false);
    }
    
    public void invertSelection(){
        dataSel.clear();
        Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
        for (int i = 0; i < d.size(); i++){
            ObrazecData od = d.get(i);
            od.selected = !od.selected;
            if(od.selected)
                dataSel.add(od);
        }
        changed(CHANGED_SELECTION, -1, false);
    }
    
    public String getFile() {
        return actDatabase;
    }
    
    public int addObrazecData(ObrazecData od) {
        int ret;
        data.add(od);
        ret = data.size() - 1;
        if(dataFiltered != null){
            dataFiltered.add(od);
            ret = dataFiltered.size() - 1;
        }
        changed(CHANGED_ALL, ret, true);
        return ret;
    }
    
    public Skladacka getActSet() {
        return actSet;
    }
    
    public boolean hasChanged() {
        return changed;
    }
    
    public void sort(Comparator<ObrazecData> comp, boolean reverse) {
        Comparator<ObrazecData> cmp = reverse ? new ReverseComparator<ObrazecData>(comp) : comp;
        if(dataFiltered != null)
            Collections.<ObrazecData>sort(dataFiltered, cmp);
        Collections.<ObrazecData>sort(data, cmp);
        changed(CHANGED_ALL, -1, true);
    }
    
    public int getIndex(Object o){
        if(o == null)
            return -1;
        Vector<ObrazecData> d = dataFiltered == null ? data : dataFiltered;
        return d.indexOf(o);
    }
    
    public int getLastSelectedIndex() {
        return dataSel.isEmpty() ? -1 : getIndex(dataSel.lastElement());
    }
    
    public ObrazecData getLastSelected() {
        return dataSel.isEmpty() ? null : dataSel.lastElement();
    /*Vector d = dataFiltered == null ? data : dataFiltered;
    for (int i = 0; i < d.size(); i++) {
      if (d.get(i).selected)
        return i;
    }*/
    }
    
    public int filter(ObrazecFilter of, ProgressListener pl){
    	Vector<ObrazecData> src, dest;
        if(of.filterType == ObrazecFilter.FILTER_NEW || of.filterType == ObrazecFilter.FILTER_ADD)
            src = data;
        else
            src = dataFiltered == null ? data : dataFiltered;
        
        boolean deselect = true;
        if(of.filterType == ObrazecFilter.FILTER_ADD && dataFiltered != null){
            dest = dataFiltered;
            deselect = false;
        } else
            dest = new Vector<ObrazecData>(src.size());
        
        if(pl != null)
            pl.progressStart(1,src.size());
        for (int i = 0; i < src.size(); i++) {
            if(pl != null)
                pl.progressChanged(i+1);
            ObrazecData od = src.get(i);
            if(((of.filterType == ObrazecFilter.FILTER_NEW || of.filterType==ObrazecFilter.FILTER_INTERSECT) && of.accept(od))
            || (of.filterType==ObrazecFilter.FILTER_SUBTRACT && !of.accept(od))
            || (of.filterType == ObrazecFilter.FILTER_ADD && of.accept(od) && !dest.contains(od))){
                dest.add(od);
            } else if (deselect){
                od.selected = false;
                //dataSel.remove(od);
            }
        }
        if(deselect)
            dataSel.retainAll(dest);
        dataFiltered = dest;
        changed(CHANGED_ALL, -1, false);
        if(pl != null)
            pl.progressEnd(true);
        return dataFiltered.size();
    }
    
    public boolean deleteSelected(){
        if(dataSel.isEmpty())
            return false;
        data.removeAll(dataSel);
        if(dataFiltered != null)
            dataFiltered.removeAll(dataSel);
        dataSel.clear();
        changed(CHANGED_ALL, -1, true);
        return true;
    }
    public boolean describeSelected(ObrazecData odDescr, Vector<String> keysRemove, ProgressListener pl){
        if(dataSel.isEmpty() || (odDescr.nazev==null && odDescr.popis==null && odDescr.autor==null
                && odDescr.datum==null && odDescr.keywords==null && keysRemove==null)){
            if(pl != null){
                skipEvent = false;
                pl.progressEnd(false);
            }
            return false;
        }
        if(pl != null)
            pl.progressStart(1,dataSel.size());
        for (int i = 0; i < dataSel.size(); i++) {
            if (pl != null)
                pl.progressChanged(i + 1);
            ObrazecData od = dataSel.get(i);
            if(odDescr.nazev != null)
                od.nazev = new String(odDescr.nazev);
            if(odDescr.popis != null)
                od.popis = new String(odDescr.popis);
            if(odDescr.autor != null)
                od.autor = new String(odDescr.autor);
            if(odDescr.datum != null)
                od.datum = new Date(odDescr.datum.getTime());
            boolean sort = false;
            if(keysRemove != null)
                if(keysRemove.isEmpty())
                    od.keywords.clear();
                else{
                od.keywords.removeAll(keysRemove);
                sort = true;
                }
            if(odDescr.keywords != null){
                od.keywords.removeAll(odDescr.keywords);
                od.keywords.addAll(odDescr.keywords);
                sort = true;
            }
            if(sort)
                Collections.sort(od.keywords);
        }
        changed(CHANGED_DESCRIPTION, -1, true);
        if(pl != null)
            pl.progressEnd(true);
        return true;
    }
    
    public boolean repair(ProgressListener pl){
        if(!data.isEmpty() && pl != null)
            pl.progressStart(1,data.size());
        for(int i=0; i<data.size(); i++){
            if (pl != null)
                pl.progressChanged(i + 1);
            ObrazecData od = data.get(i);
            Dilek.normalizeAll(od.d);
            od.o = new Obrys(od.d);
        }
        changed(CHANGED_OBRAZEC, -1, true);
        if(pl != null)
            pl.progressEnd(true);
        return true;
    }
    
    public void zrusFilter(){
        dataFiltered = null;
        changed(CHANGED_ALL, -1, false);
    }
    public int selectedCount(){
        return dataSel.size();
    }
    public int filteredCount(){
        return dataFiltered == null ? data.size() : dataFiltered.size();
    }
    
    public boolean copySelection(){
        if(dataSel.isEmpty())
            return false;
        if(clipboard == null)
            clipboard = new Vector<ObrazecData>(dataSel.size());
        else{
            clipboard.clear();
            clipboard.ensureCapacity(dataSel.size());
        }
        clipboard.addAll(dataSel);
        //clipboard.trimToSize();
        clipboardSet = actSet;
        return true;
    }
    
    public boolean isSelected(){
        return dataSel != null && !dataSel.isEmpty();
    }
    
    public boolean isFiltered(){
        return dataFiltered != null;
    }
    
    public boolean isPastable(){
        return clipboard!=null && !clipboard.isEmpty() && clipboardSet.nazev.equals(actSet.nazev);
    }
    
    public int pastSelection(){
        if(!clipboardSet.nazev.equals(actSet.nazev))
            return 0;
        selectAll(false);
        if(clipboard.isEmpty())
            return 0;
        dataSel.ensureCapacity(clipboard.size());
        for(int i=0; i<clipboard.size(); i++){
            ObrazecData od = new ObrazecData(clipboard.get(i));
            od.selected = true;
            dataSel.add(od);
        }
        data.addAll(dataSel);
        if(dataFiltered != null)
            dataFiltered.addAll(dataSel);
        changed(CHANGED_ALL, -1, true);
        return dataSel.size();
    }
    public boolean needsSaveAs(){
        return actDatabase == null;
    }

    public ObrazecData getDefaultObrazec(){
        return defObrazce.get(actSet.nazev);
    }
    public ObrazecData getDefaultObrazec(String set){
        return defObrazce.get(set);
    }
    
    public ObrazecData getDefaultObrazecCopy(){
        return getDefaultObrazecCopy(actSet.nazev);
    }

    public ObrazecData getDefaultObrazecCopy(String set){
        ObrazecData def = defObrazce.get(set);
        return new ObrazecData(def);
    }

    private ObrazecData loadFirstObrazec(InputStream is, Skladacka skl){
        ObrazecData obd = null;
        try {
            DataInputStream in = new DataInputStream(is);
            String s = in.readUTF();
            if(!s.equals(skl.nazev))
                throw new IllegalStateException();
            int count = in.readInt();
            if(count>0){
                obd = new ObrazecData(skl);
                obd.nacti(in);
            }
            else {
                obd = new ObrazecData(skl,skl.getDilky(),null);
            }
            in.close();
        }
        catch(Exception ex){
            Dilek d[] = skl.getDilky();
            Dilek.vycentruj(d);
            obd = new ObrazecData(skl,d);
        }
        return obd;
    }
    
    private ObrazecData loadFirstObrazec(Skladacka skl, String fData, Class<? extends Object> cl){
        try{
            InputStream is = cl == null ? new FileInputStream(fData) : getClass().getResourceAsStream(fData);
            return loadFirstObrazec(is, skl);
        }catch(FileNotFoundException ex){}
        return null;
    }
    
    private void loadDefaultObrazec(Class<? extends Object> cl){
        defObrazce = new HashMap<String,ObrazecData>(skladacky.size());
        for(Skladacka skl: skladacky.values()){
            defObrazce.put(skl.nazev, loadFirstObrazec(skl, skl.getDefDatabase(),cl));
            Skladacka ss = skl.getSuperSet();
            if(ss != null)
                defObrazce.put(ss.nazev, loadFirstObrazec(ss, ss.getDefDatabase(),cl));
        }
    }
    boolean skipEvent = false;
    public void ignoreNextEvent(){
        skipEvent = true;
    }
    public void setEvents(boolean jak){
        events = jak;
    }
}
